创建注册中心
主程序开启注册中心功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.itguigu.zcw;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@EnableEurekaServer @SpringBootApplication public class ZcwRegisterApplication { public static void main (String[] args) { SpringApplication.run(ZcwRegisterApplication.class , args ) ; } }
添加 application.yml 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 spring: application: name: ZCW-REGISTER server: port: 8761 eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
创建用户模块 创建一个 Spring Starter Project,zcw-user。功能主要是注册,登录,会员中心,密码找回等。需要加上 web, MySQL ,JDBC, MyBatis, Eureka Discovery, Redis 等相关模块。
创建项目模块 创建一个 Spring Starter Project,zcw-project。功能主要是众筹项目的发布,创建等。需要加上 web, MySQL ,JDBC, MyBatis, Eureka Discovery, Redis 等相关模块。
步骤和创建用户模块差不多,图略
创建订单模块 创建一个 Spring Starter Project,zcw-order。功能和支付相关。需要加上 web, MySQL ,JDBC, MyBatis, Eureka Discovery, Redis 等相关模块。
步骤和创建用户模块差不多,图略
创建公共模块 创建一个 Maven 工程 (jar),zcw-commons(以前已经创建 zcw-common 项目了,加个 s )。不继承父工程,因为父项目要依赖于当前项目,否则出现循环依赖。
创建父工程 创建一个 Maven 工程 (pom),zcw-parents(以前已经创建 zcw-parent 项目了,加个 s ),父工程继承 SpringBoot 父工程,其他自定义项目继承 zcw-parents 项目,并且聚合其他自定义项目,父工程依赖于 zcw-commons 工程, 注意不是依赖管理。其他项目就不用再配置对zcw-commons 项目的依赖了。
父工程继承 SpringBoot 父工程
1 2 3 4 5 6 7 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.6.RELEASE</version > <relativePath /> </parent >
修改每个子工程的配置文件,让子工程继承父工程
1 2 3 4 5 6 7 <parent > <groupId > com.atguigu.zcw</groupId > <artifactId > zcw-parents</artifactId > <version > 0.0.1-SNAPSHOT</version > <relativePath > ../zcw-parents/pom.xml</relativePath > </parent >
父工程聚合子工程,并且依赖 commons 工程
集成 Druid 在父工程中增加对 druid 数据源依赖
1 2 3 4 5 6 <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.1.12</version > </dependency >
在 user 模块中配置 application.yml 配置文件,配置数据库连接信息,eurake 等信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 spring: application: name: SCW-USER datasource: username: root password: 123456 url: jdbc:mysql://127.0.0.1:3306/zcw?useSSL=false&useUnicode=true&characterEncoding=UTF-8 driver-class-name: com.mysql.jdbc.Driver mybatis: config-location: classpath:/mybatis/mybatis-config.xml mapper-locations: classpath:/mybatis/mapper/*.xml eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: appname: SCW-USER prefer-ip-address: true server: port: 7000
配置 Druid 数据源配置类,一方面是让其读取 yml 中的配置文件,创建 DataSource 实例。一方面是配置 Druid 监控。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package com.itguigu.zcw.user.config;import java.sql.SQLException;import java.util.Arrays;import java.util.HashMap;import java.util.Map;import javax.sql.DataSource;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import com.alibaba.druid.pool.DruidDataSource;import com.alibaba.druid.support.http.StatViewServlet;import com.alibaba.druid.support.http.WebStatFilter;@Configuration public class AppDruidConfig { @ConfigurationProperties (prefix = "spring.datasource" ) @Bean public DataSource dataSource () throws SQLException { DruidDataSource dataSource = new DruidDataSource(); dataSource.setFilters("stat" ); return dataSource; } @Bean public ServletRegistrationBean statViewServlet () { ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*" ); Map<String, String> initParams = new HashMap<>(); initParams.put("loginUsername" , "admin" ); initParams.put("loginPassword" , "123456" ); initParams.put("allow" , "" ); bean.setInitParameters(initParams); return bean; } @Bean public FilterRegistrationBean webStatFilter () { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions" , "*.js,*.css,/druid/*" ); bean.setInitParameters(initParams); bean.setUrlPatterns(Arrays.asList("/*" )); return bean; } }
在 resource 资源文件夹下新建 mybatis 文件夹, mybatis 文件夹下新建 mapper 文件夹, mybatis 文件夹下创建 mybatis-config.xml 文件, 内容如下
1 2 3 4 5 6 7 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration > </configuration >
主程序上加上包扫描和开启服务注册发现和事务等功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.itguigu.zcw;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.transaction.annotation.EnableTransactionManagement;@EnableTransactionManagement @MapperScan ("com.itguigu.zcw.user.mapper" )@EnableDiscoveryClient @SpringBootApplication public class ZcwUserApplication { public static void main (String[] args) { SpringApplication.run(ZcwUserApplication.class , args ) ; } }
测试数据源信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.itguigu.zcw;import java.sql.Connection;import java.sql.SQLException;import javax.sql.DataSource;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest class ZcwUserApplicationTests { @Autowired DataSource dataSource; @Test void testDatasource () throws SQLException { Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); } }
集成 slf4j+logback SpringBoot 底层默认使用的就是 slf4j+logback 日志框架,所以我们只需要添加 logback.xml 文件即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8"?> <configuration > <appender name ="STDOUT" class ="ch.qos.logback.core.ConsoleAppender" > <encoder > <pattern > %-4relative [%thread] %-5level %logger{35} - %msg %n</pattern > </encoder > </appender > <root level ="DEBUG" > <appender-ref ref ="STDOUT" /> </root > </configuration >
集成 Redis 通过以下两个类可以查看 redis-starter 的相关信息org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguratio
org.springframework.boot.autoconfigure.data.redis.RedisProperties
创建项目的时候已经引入了 redis-starter,所以不必再次添加,直接在 yml 中配置 redis 地址即可。RedisTemplate(可以操作对象)、StringRedisTemplate(建议用这个, 将对象整成json字符串对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 spring: application: name: SCW-USER datasource: username: root password: 123456 url: jdbc:mysql://127.0.0.1:3306/zcw?useSSL=false&useUnicode=true&characterEncoding=UTF-8 driver-class-name: com.mysql.jdbc.Driver redis: host: 127.0 .0 .1 port: 6379 mybatis: config-location: classpath:/mybatis/mybatis-config.xml mapper-locations: classpath:/mybatis/mapper/*.xml eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: appname: SCW-USER prefer-ip-address: true server: port: 7000
测试 redis 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test void testRedis () { stringRedisTemplate.opsForValue().set("test" , "test value" ); String string = stringRedisTemplate.opsForValue().get("test" ); System.out.println(string); redisTemplate.opsForValue().set("zhangsan" , "zhangsan" ); Object object = redisTemplate.opsForValue().get("zhangsan" ); System.out.println(object); }
集成 Swagger 在父工程中添加依赖
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > io.springfox</groupId > <artifactId > springfox-swagger2</artifactId > <version > 2.9.2</version > </dependency > <dependency > <groupId > io.springfox</groupId > <artifactId > springfox-swagger-ui</artifactId > <version > 2.9.2</version > </dependency >
在 user 模块下的 com.itguigu.zcw.user.config 包中编写配置类,开启 swagger2 自动生成 api 文档的功能
1 2 3 4 5 6 7 8 9 10 11 package com.itguigu.zcw.user.config;import org.springframework.context.annotation.Configuration;import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration @EnableSwagger 2 public class AppSwaggerConfig {}
启动项目,访问地址 http://127.0.0.1:7000/swagger-ui.html 就能看到 swagger 的页面了
导入已经设计好的接口文档信息 接口文档和 Controller 已经在另一个项目中设计好了,直接导入
集成 lombok lombok 外号叫做小辣椒, 因为 logo 是小辣椒的样子。https://www.projectlombok.org/
common 项目中引入 lombok:
1 2 3 4 5 6 <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.8</version > </dependency >
复制 maven 仓库中的projectlombok/lombok/1.18.12/
下的 lombok-1.18.12.jar
包到 STS 的安装目录中,并去掉 jar 包的版本号,且在 STS.ini 文件中最后一行加上一下内容,配置方法参考 此处
1 2 -javaagent:../Eclipse/lombok.jar -vmargs -javaagent:lombok.jar
重启 STS,导入对应包即可。lombok 常见注解如下:@Data
:提供getter/setter@NoArgsConstructor
: 无参构造器 @RequiredArgsConstructor @AllArgsConstructor 全参数构造器@EqualsAndHashCode
:提供equals和hashCode方法@Log
:快速的使用slf4j日志@Log4j
:快速使用log4j日志@Log4j2
:快速使用log4j2@Getter/@Setter
@Slf4j
内置log对象,直接调用日志方法输出日志@ToString
HttpClient HttpClient 版本已经停止使用,被 HttpComponents 取代。HttpUtils 工具类代码参考地址 ,依赖 。在 commons 模块中,com.itguigu.zcw.http 包下创建 HttpUtils 类,将 github上中的代码拷贝其中,并在 pom 文件中引入依赖。
接口统一返回 在 commons 模块中,com.itguigu.zcw.vo.resp 包下创建 AppResponse。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.itguigu.zcw.vo.resp;import com.itguigu.zcw.enums.ResponseCodeEnume;import lombok.Data;@Data public class AppResponse <T > { private Integer code; private String msg; private T data; public static <T> AppResponse<T> ok (T data) { AppResponse<T> resp = new AppResponse<>(); resp.setCode(ResponseCodeEnume.SUCCESS.getCode()); resp.setMsg(ResponseCodeEnume.SUCCESS.getMsg()); resp.setData(data); return resp; } public static <T> AppResponse<T> fail (T data) { AppResponse<T> resp = new AppResponse<>(); resp.setCode(ResponseCodeEnume.FAIL.getCode()); resp.setMsg(ResponseCodeEnume.FAIL.getMsg()); resp.setData(data); return resp; } }
封装短信接口 采用阿里云第三方短信接口 进行短信的发送,封装如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package com.itguigu.zcw.user.components;import java.util.HashMap;import java.util.Map;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.util.EntityUtils;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import com.itguigu.zcw.http.HttpUtils;import com.itguigu.zcw.vo.resp.AppResponse;import lombok.extern.slf4j.Slf4j;@Slf 4j@Component public class SmsTemplate { @Value ("${sms.host}" ) String host ; @Value ("${sms.path}" ) String path ; @Value ("${sms.method}" ) String method ; @Value ("${sms.appcode}" ) String appcode ; public AppResponse<String> sendCode (Map<String, String> querys) { log.debug("开始发送短信-参数:{}" , querys); Map<String, String> headers = new HashMap<String, String>(); headers.put("Authorization" , "APPCODE " + appcode); Map<String, String> bodys = new HashMap<String, String>(); try { HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys); System.out.println(response.toString()); HttpEntity entity = response.getEntity(); System.out.println(EntityUtils.toString(response.getEntity())); log.debug("开始发送短信-成功:{},{}" , querys.get("mobile" ),querys.get("param" )); return AppResponse.ok("OK" ); } catch (Exception e) { log.debug("开始发送短信-失败:{}" , e.getMessage()); return AppResponse.fail("fail" ); } } }
使用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @ApiOperation (value = "发送短信验证码" ) @PostMapping ("/sendsms" )public AppResponse<Object> sendsms (String number) { StringBuffer code = new StringBuffer(); Random random = new Random(); for (int i = 0 ; i < 4 ; i++) { code.append(random.nextInt(10 )); } HashMap<String, String> querys = new HashMap<>(); querys.put("mobile" , number); querys.put("param" , "code:" + code.toString()); querys.put("tpl_id" , "TP1711063" ); AppResponse<String> status = smsTemplate.sendCode(querys); if (0 == status.getCode()) { stringRedisTemplate.opsForValue().set(number, code.toString()); log.info("发送短信成功!" ); return AppResponse.ok(status.getMsg()); }else { log.info("发送短信失败!" ); return AppResponse.fail(status.getMsg()); } }
逆向工程 数据库表设计好之后,可以使用 mbg 插件逆向生成 Vo。在父工程 pom 中引入 mbg 插件(这样工程也能使用)
1 2 3 4 5 6 <dependency > <groupId > org.mybatis.generator</groupId > <artifactId > mybatis-generator-core</artifactId > <version > ${mbg.version}</version > </dependency >
引入 xml 文件,并配置好数据库账号密码,要逆向的表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration > <context id ="MySQLTables" targetRuntime ="MyBatis3" > <commentGenerator > <property name ="suppressAllComments" value ="true" /> </commentGenerator > <jdbcConnection driverClass ="com.mysql.jdbc.Driver" connectionURL ="jdbc:mysql://127.0.0.1:3306/zcw?useSSL=false" userId ="root" password ="123456" > </jdbcConnection > <javaTypeResolver > <property name ="forceBigDecimals" value ="false" /> </javaTypeResolver > <javaModelGenerator targetPackage ="com.itguigu.zcw.user.bean" targetProject ="../zcw-user/src/main/java" > <property name ="enableSubPackages" value ="true" /> <property name ="trimStrings" value ="true" /> </javaModelGenerator > <sqlMapGenerator targetPackage ="mybatis.mapper" targetProject ="../zcw-user/src/main/resources" > <property name ="enableSubPackages" value ="true" /> </sqlMapGenerator > <javaClientGenerator type ="XMLMAPPER" targetPackage ="com.itguigu.zcw.user.mapper" targetProject ="../zcw-user/src/main/java" > <property name ="enableSubPackages" value ="true" /> </javaClientGenerator > <table tableName ="t_member" > </table > <table tableName ="t_member_address" > </table > <table tableName ="t_member_cert" > </table > <table tableName ="t_member_project_follow" > </table > <table tableName ="t_message" > </table > </context > </generatorConfiguration >
在父工程中(这样工程也能使用) pom 中配置和 Maven 做集成的插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <build > <plugins > <plugin > <groupId > org.mybatis.generator</groupId > <artifactId > mybatis-generator-maven-plugin</artifactId > <version > 1.3.7</version > <dependencies > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.42</version > </dependency > </dependencies > </plugin > </plugins > </build >
执行 Maven 命令 mybatis-generator:generate
生成相关信息
第一次运行出现以下错误信息
1 [ERROR] Failed to execute goal on project zcw-user: Could not resolve dependencies for project com.itguigu.zcw:zcw-user:jar:0.0.1-SNAPSHOT: Could not find artifact com.atguigu.zcw:zcw-commons:jar:0.0.1-SNAPSHOT -> [Help 1]
原因是 user 继承类父模块,父模块依赖了 commons 模块,我们只需执行 Maven 命令 install 一下 commons 模块即可。然后重新运行 mybatis-generator:generate
就能生成相关的 mapper 和实体信息。
注册 新建 com.itguigu.zcw.user.vo.req 包,用于存放注册请求参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.itguigu.zcw.user.vo.req;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.Data;import lombok.ToString;@ApiModel @Data @ToString public class UserRegistVo { @ApiModelProperty ("手机号" ) private String loginacct; @ApiModelProperty ("密码" ) private String userpaswd; @ApiModelProperty ("邮箱" ) private String email; @ApiModelProperty ("验证码" ) private String code; @ApiModelProperty ("用户类型 0-个人,1-企业" ) private String usertype; }
controller 方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 @ApiOperation (value = "用户注册" ) @PostMapping ("/register" ) public AppResponse<Object> register (UserRegistVo userRegistVo) { log.info("用户注册,接收到请求参数:{}" , userRegistVo); String loginacct = userRegistVo.getLoginacct(); if (!StringUtils.isEmpty(loginacct)) { String code = stringRedisTemplate.opsForValue().get(loginacct); if (!StringUtils.isEmpty(code) && code.equals(userRegistVo.getCode())) { boolean isEmail = ValidationEmail.isEmail(userRegistVo.getEmail()); if (!isEmail) { AppResponse<Object> resp = AppResponse.fail(null ); resp.setMsg("邮箱格式不正确" ); return resp; } try { Boolean emailExist = userService.emailExist(userRegistVo.getEmail()); if (emailExist) { AppResponse<Object> resp = AppResponse.fail(null ); resp.setMsg("邮箱已存在" ); return resp; } Boolean loginExist = userService.loginacctExist(userRegistVo.getUserpaswd()); if (emailExist) { AppResponse<Object> resp = AppResponse.fail(null ); resp.setMsg("账号已被注册" ); return resp; } int i = userService.saveMember(userRegistVo); if (i==1 ) { stringRedisTemplate.delete(loginacct); AppResponse<Object> resp = AppResponse.ok("ok" ); resp.setMsg("注册成功" ); return resp; }else { AppResponse<Object> resp = AppResponse.fail(null ); resp.setMsg("注册失败" ); return resp; } } catch (Exception e) { log.error("注册失败, 系统错误:{}" , e.getMessage()); AppResponse<Object> resp = AppResponse.fail(null ); resp.setMsg("系统错误" ); return resp; } }else { AppResponse<Object> resp = AppResponse.fail(null ); resp.setMsg("验证码已经过期, 请重新获取" ); return resp; } }else { AppResponse<Object> resp = AppResponse.fail(null ); resp.setMsg("请输入注册手机号" ); return resp; } }
UserService.impi 实现如下,里面的 BeanUtils.copyProperties
是将 Vo 转换为 bean 实体对象的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package com.itguigu.zcw.user.service.impl;import java.util.List;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.stereotype.Service;import com.itguigu.zcw.user.bean.TMember;import com.itguigu.zcw.user.bean.TMemberExample;import com.itguigu.zcw.user.bean.TMemberExample.Criteria;import com.itguigu.zcw.user.enums.UserExceptionEnum;import com.itguigu.zcw.user.exception.UserException;import com.itguigu.zcw.user.mapper.TMemberMapper;import com.itguigu.zcw.user.service.UserService;import com.itguigu.zcw.user.vo.req.UserRegistVo;import lombok.extern.slf4j.Slf4j;@Slf 4j@Service public class UserServiceImpl implements UserService { @Autowired TMemberMapper memberMapper; @Override public Boolean emailExist (String email) { try { TMemberExample tMemberExample = new TMemberExample(); Criteria tmemCriteria = tMemberExample.createCriteria(); tmemCriteria.andEmailEqualTo(email); List<TMember> memberList = memberMapper.selectByExample(tMemberExample); return memberList.size()>0 ? true : false ; } catch (Exception e) { throw new UserException(UserExceptionEnum.EMAIL_EXIST); } } @Override public int saveMember (UserRegistVo userRegistVo) { try { TMember tMember = new TMember(); BeanUtils.copyProperties(userRegistVo, tMember); BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String bCryptPassword = bCryptPasswordEncoder.encode(userRegistVo.getUserpaswd()); tMember.setUserpswd(bCryptPassword); tMember.setUsername(userRegistVo.getLoginacct()); return memberMapper.insertSelective(tMember); } catch (Exception e) { log.error("新增用户失败:{}" , e.getMessage()); throw new UserException(UserExceptionEnum.REGIST_ERROR); } } @Override public Boolean loginacctExist (String userpaswd) { try { TMemberExample tMemberExample = new TMemberExample(); Criteria tmemCriteria = tMemberExample.createCriteria(); tmemCriteria.andUserpswdEqualTo(userpaswd); List<TMember> memberList = memberMapper.selectByExample(tMemberExample); return memberList.size()>0 ? true : false ; } catch (Exception e) { throw new UserException(UserExceptionEnum.LOGINACCT_EXIST); } } }
这里面还涉及自定义错误和错误枚举,处理接口统一返回等问题,值得看看。
文件上传 OSS 在 zcw-project 模块下参考阿里云 OSS SDK 编写AliOssTemplate 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.itguigu.zcw.components;import java.io.InputStream;import java.util.UUID;import com.aliyun.oss.OSS;import com.aliyun.oss.OSSClientBuilder;import lombok.Data;import lombok.ToString;import lombok.extern.slf4j.Slf4j;@Slf 4j@ToString @Data public class AliOssTemplate { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; private String uploadFolderName; public String upload (InputStream inputStream, String uploadFileName) { log.info("endpoint:{}" , endpoint); log.info("accessKeyId:{}" , accessKeyId); log.info("accessKeySecret:{}" , accessKeySecret); log.info("bucketName:{}" , bucketName); log.info("uploadFolderName:{}" , uploadFolderName); log.info("uploadFileName:{}" , uploadFileName); String fileName; try { OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); String fileSuffix = UUID.randomUUID().toString().replace("-" , "" ); fileName = fileSuffix + "_" + uploadFileName; ossClient.putObject(bucketName, uploadFolderName + "/" + fileName, inputStream); ossClient.shutdown(); } catch (Exception e) { return "" ; } return "https://" + bucketName + ".oss-cn-shanghai.aliyuncs.com/" + uploadFolderName + "/" + fileName; } }
这里的配置采用配置类的方式注入,不是之前的 @Value
, 配置类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.itguigu.zcw.config;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import com.itguigu.zcw.components.AliOssTemplate;@Configuration public class AliOssConfig { @ConfigurationProperties (prefix = "oss" ) @Bean public AliOssTemplate aliOssTemplate () { return new AliOssTemplate(); } }
controller 代码如下,这里可以接受上传的多个文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @ApiOperation (value = "上传图片" ) @PostMapping ("/upload" ) public AppResponse<Object> upload (@RequestParam("files" ) MultipartFile[] files) { ArrayList<String> fileUrlList = new ArrayList<>(); for (MultipartFile file : files) { try { String fileUrl = aliOssTemplate.upload(file.getInputStream(), file.getOriginalFilename()); fileUrlList.add(fileUrl); } catch (IOException e) { log.error("文件上传错误:{}" , file.getOriginalFilename()); e.printStackTrace(); AppResponse<Object> resp = AppResponse.fail("" ); resp.setMsg("文件上传失败" ); } } return AppResponse.ok(fileUrlList); }
项目创建 创建众筹的项目步骤是一步一步的,数据是在最后一步才提交的,所以我们要临时存储中间每一步的数据。所以每一个步骤的提交,参数都由一个小 Vo 进行收集,然后将小 Vo 整合到一个大 Vo 当中去,提交的最后一步得到一个信息完整的大 Vo,最后将大 Vo 转换成一个个需要存储的实体类进行保存,且这些保存都是在一个事务里面的。这里将收集每一个 Vo 的信息放入 Redis 进行临时存储,以便于传到下一个步骤,具体的需要用到 fastjson 的序列化和反序列操作(将对象转换为字符串,将字符串转换为对象)。fastjson 应用 string字符串转换成java对象或者对象数组
支付宝支付 支付宝支付需要用到 RSA2 密钥,需要将自己的公钥上传支付宝,私钥留着对参数进行加密。还要获取支付宝的公钥,对支付宝返回来的数据进行验签,防止数据在我发送给支付宝或者支付宝返回给我的时候遭到篡改。
SpringSession 解决 session 不一致有很多方案,但多配置复杂或者有明显的缺点。有了 SpringSession,所有的 session 由SpringSession 创建维护,无需我们修改任何代码,就能在集群环境下使用原生的 session 方式编程,无侵入、简单配置和 Spring 应用无缝整合、对接各种 session 存储方案。
新建项目,并且引入以下依赖:
application.properties 中进行配置:
1 2 3 4 5 6 7 8 9 10 11 # redis配置 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.jedis.pool.max-idle=100 # springsession配置 spring.session.store-type=redis # session过期时间 spring.session.timeout=1800
编写 controller 代码实现 session 的插入和读取。注意⚠️ 这里引入 spring session 之后,原来的 HttpSession 就被重写了,重写后操作的不在是以前的 session 域。(写法还和以前一样,但是操作的不是 session 域了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.itguigu.zcw.controller;import javax.servlet.http.HttpSession;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class SpringSessionController { @GetMapping ("/get" ) public String get (HttpSession session) { String attributeStr = (String) session.getAttribute("test" ); System.out.println(session.getClass()); return attributeStr; } @GetMapping ("/set" ) public String set (HttpSession session) { session.setAttribute("test" , "test spring session" ); System.out.println(session.getClass()); return "ok" ; } }
访问 http://localhost:8080/get,http://localhost:8080/set 进行测试。
Thymeleaf 官网地址:https://www.thymeleaf.org/ 。 搭建 web 项目。使用 Thymeleaf 和前端页面进行整合,并使用 Feign 调用后面的微服务。
demo 继承父工程
1 2 3 4 5 6 7 <parent > <groupId > com.atguigu.zcw</groupId > <artifactId > zcw-parents</artifactId > <version > 0.0.1-SNAPSHOT</version > <relativePath > ../zcw-parents/pom.xml</relativePath > </parent >
配置文件中进行相应配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 spring: application: name: SCW-WEB thymeleaf: prefix: classpath:/templates/ suffix: .html cache: false session: store-type: redis timeout: 1800 redis: host: 127.0 .0 .1 port: 6379 jedis: pool: max-idle: 100 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: appname: SCW-WEB prefer-ip-address: true server: port: 7005 feign: hystrix: enabled: true
创建 Controller 和 前端页面,并在页面中取值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.itguigu.zcw.web.controller;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;@Controller public class DispatcherController { @RequestMapping ("/index" ) public String index (Model model) { model.addAttribute("key" , "value" ); return "index" ; } }
注意: 在页面中需要引入 xml 的命名空间,值是 http://www.thymeleaf.org 1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html> <html xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > thymeleaf 测试</title > </head > <body > <div th:text ="${key}" > div 内容 </div > </body > </html >
访问地址 http://localhost:7005/index 就能看到返回的内容。
常用语法 th:text
1 <div th:text ="${key}" > div 内容</div >
th:each
1 <div th:each ="user: ${users}" th:text ="${user}" > </div >
1 <div th:each ="user: ${users}" > [[${user}]]</div >
加载静态文件,下面例子中的 href 和 src 都是原生的属性,th:href 和 th:src 的值会覆盖原生属性的值,@{/}
这种写法自动的项目根路径下加载文件。因为 th:href 和 th:src 会覆盖原有的 href 和 src 属性,所以这里不要原有的 href 和 src 属性也行,如果不要就意味着只能以这种模版渲染的方式运行,直接用浏览器打开这个静态的页面是没有效果的。所以最好还是两种方式都使用。
1 <link href ="/static/bootstrap/css/bootstrap.css" th:href ="@{/static/bootstrap/css/bootstrap.css}" />
1 <script type ="text/javascript" src ="/static/static/bootstrap/js/bootstrap.min.js" th:src ="@{/static/bootstrap/js/bootstrap.min.js}" > </script >
其他常见语法课参考此处 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.itguigu.zcw.web.config;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;public class AppWebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { } @Override public void addViewControllers (ViewControllerRegistry registry) { registry.addViewController("/index" ).setViewName("index" ); } }
还可以配置资源过滤,跨域等信息。
多环境配置 只需要在创建多个 yml 配置文件,然后在 properties 中指定启用的哪个环境就行。例如存在以下配置文件
1 2 3 4 5 drwxr-xr-x 5 rex staff 160 5 1 15:16 ./ drwxr-xr-x 5 rex staff 160 5 1 15:18 ../ -rw-r--r-- 1 rex staff 376 4 10 16:12 application-dev.yml -rw-r--r-- 1 rex staff 376 5 1 15:17 application-prod.yml -rw-r--r-- 1 rex staff 47 5 1 15:19 application.properties
只需要在 application.properties 中指定使用哪一个配置文件即可
1 2 # 指定启用环境 spring.profiles.active=dev
项目打包部署 可以直接用 STS 的 Maven 插件,直接输入 package,或者直接在命令行运行 mvn package 运行即可。在对其他项目打包之前,需要把 common 项目先安装到仓库中,因为其他项目依赖 common 项目。
安装依赖项目
1 mvn clean install -Dmaven.test.skip=true
打包
或者跳过运行测试用例进行打包
1 mvn package -Dmaven.test.skip=true
maven 各命令的区别和声明周期:
package命令完成了项目编译、单元测试、打包功能,但没有把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库
install命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库,但没有布署到远程maven私服仓库
deploy命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库
所以在进行项目打包的时候:
先对 common 项目进行打包,并安装到本地 maven 仓库,这样其他工程才能找到
1 mvn install -Dmaven.test.skip=true # install 就做了 package 的工作,所以不用单独执行 install
修改各个工程的配置文件
对订单服务进行打包
1 mvn package -Dmaven.test.skip=true
对注册中心进行打包
1 mvn package -Dmaven.test.skip=true
对用户模块进行打包
1 mvn package -Dmaven.test.skip=true
对 web 模块进行打包
1 mvn package -Dmaven.test.skip=true
对项目模块进行打包
1 mvn package -Dmaven.test.skip=true
父工程不用打包
最后将打好的五个包分别从 target 中拷贝出来
启动项目
1 2 3 4 5 nohup java -jar zcw-register-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > ./zcw-register.log & nohup java -jar zcw-order-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > ./zcw-order.log & nohup java -jar zcw-project-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > ./zcw-project.log & nohup java -jar zcw-user-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > ./zcw-user.log & nohup java -jar zcw-web-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > ./zcw-web.log &